package storager

import (
	"context"
	"errors"
	"gf_blog_admin/internal/consts"
	"gf_blog_admin/internal/dao"
	"gf_blog_admin/internal/model/entity"
	"gf_blog_admin/utility/utils"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gfile"
	"strings"
	"time"
)

const (
	UploadDriveLocal = "local"
	UploadDriveQiNiu = "qiniu"
	// TaskExpireIn 分片上传任务有效期
	TaskExpireIn = time.Hour * 24 * 7
	// CacheMPUpload 分片上传
	CacheMPUpload = "mp_upload"
)

// 上传配置
var config *UploadConfig

// SetConfig 设置配置
func SetConfig(c *UploadConfig) {
	config = c
}

// GetConfig 获取配置
func GetConfig() *UploadConfig {
	return config
}

// NewUpload 初始化存储驱动
func NewUpload(name string) UploadDrive {
	var (
		driveType = UploadDriveLocal
		drive     UploadDrive
	)
	if name != "" {
		driveType = name
	}
	switch driveType {
	case UploadDriveLocal:
		drive = NewLocalDrive(config.LocalConfig)
	case UploadDriveQiNiu:
		drive = NewQiNiuDrive(config.QiNiuConfig)
	default:
		panic(errors.New("暂不支持的存储驱动"))
	}
	return drive
}

// GetQiNiuDrive 获取七牛存储驱动
func GetQiNiuDrive() *QiNiuDrive {
	return NewQiNiuDrive(config.QiNiuConfig)
}

// DoUpload 直接上传
func DoUpload(ctx context.Context, file *ghttp.UploadFile) (result *entity.BlFile, err error) {
	meta := &FileMeta{
		Filename: file.Filename,
		Size:     file.Size,
		MimeType: file.Header.Get("Content-Type"),
		Ext:      Ext(file.Filename),
	}
	meta.Kind = ExtensionToKind(meta.Ext)
	if err = validateFileMeta(meta); err != nil {
		return nil, err
	}
	obj, err := NewUpload(config.Drive).Upload(ctx, file)
	if err != nil {
		return nil, err
	}
	meta.Md5 = obj.Meta.Md5
	if result, err = hasFile(ctx, obj.Key); err != nil {
		return nil, err
	} else if result != nil {
		return result, nil
	}
	obj.Meta = meta
	return write(ctx, obj)
}

func DoDestroy(ctx context.Context, key, drive string) error {
	return NewUpload(drive).Destroy(ctx, key)
}

// LastUrl 根据驱动获取最终文件访问地址
func LastUrl(ctx context.Context, key, drive string) string {
	if utils.IsURL(key) {
		return key
	}
	return NewUpload(drive).Location(ctx, key)
}

// InitMultipart 初始化分片上传
func InitMultipart(ctx context.Context) (mp *MultipartProgress, err error) {
	return NewMultipart(config.LocalConfig).InitiateMultipart(ctx)
}

// UploadPart 上传分片
func UploadPart(ctx context.Context, in *UploadPartParams) (result *MultipartItem, err error) {
	var partMaxSize int64 = 1024
	if config.PartMaxSize > 0 {
		partMaxSize = int64(config.PartMaxSize)
	}
	partMaxSize = partMaxSize * 1024 * 1024
	if in.File.Size > partMaxSize {
		return nil, errors.New("单个分片大小不能超过" + gfile.FormatSize(partMaxSize))
	}
	if in.PartNumber < 1 || in.PartNumber > 10000 {
		return nil, errors.New("超出partNumber范围（1~10000）")
	}
	return NewMultipart(config.LocalConfig).UploadPart(ctx, in)
}

// CompleteMultipart 完成上传分片
func CompleteMultipart(ctx context.Context, in *CheckMultipartParams) (result *entity.BlFile, err error) {
	obj, err := NewMultipart(config.LocalConfig).CompleteMultipart(ctx, in)
	if err != nil {
		return nil, err
	}
	if result, err = hasFile(ctx, obj.Key); err != nil {
		return nil, err
	} else if result != nil {
		return result, nil
	}
	return write(ctx, obj)
}

// AbortMultipart 删除分片任务
func AbortMultipart(ctx context.Context, uploadId string) error {
	return NewMultipart(config.LocalConfig).AbortMultipart(ctx, uploadId)
}

// ListParts 列举已上传分片
func ListParts(ctx context.Context, uploadId string) (mp *MultipartProgress, err error) {
	return NewMultipart(config.LocalConfig).ListParts(ctx, uploadId)
}

// 验证上传文件
func validateFileMeta(meta *FileMeta) error {
	ext := ExtName(meta.Ext)
	switch meta.Kind {
	case KindImg:
		if !IsImgType(meta.Ext) {
			return errors.New("上传的文件不是图片")
		}
		config.ImageSize = config.ImageSize * 1024 * 1024
		if config.ImageSize > 0 && meta.Size > config.ImageSize {
			return errors.New("图片大小不能超过" + gfile.FormatSize(config.ImageSize))
		}
		if len(config.ImageType) > 0 && !utils.InSlice(strings.Split(config.ImageType, `,`), ext) {
			return errors.New("上传图片类型未经允许")
		}
	case KindAudio:
		if !IsAudioType(meta.Ext) {
			return errors.New("上传的文件不是音频")
		}
	case KindVideo:
		if !IsVideoType(meta.Ext) {
			return errors.New("上传的文件不是视频")
		}
	case KindZip:
		if !IsZipType(meta.Ext) {
			return errors.New("上传的文件不是压缩文件")
		}
	case KindDoc:
		if !IsDocType(meta.Ext) {
			return errors.New("上传的文件不是文档")
		}
	case KindOther:
		fallthrough
	default:
		// 默认为通用的文件上传
		config.FileSize = config.FileSize * 1024 * 1024
		if config.FileSize > 0 && meta.Size > config.FileSize {
			return errors.New("文件大小不能超过" + gfile.FormatSize(config.FileSize))
		}
		if len(config.FileType) > 0 && !utils.InSlice(strings.Split(config.FileType, `,`), ext) {
			return errors.New("上传文件类型未经允许")
		}
	}
	return nil
}

// 检查请求头md5
func hasContentMd5(ctx context.Context, md5 string) error {
	hMd5 := ghttp.RequestFromCtx(ctx).Header.Get("Content-MD5")
	if hMd5 != "" && hMd5 != md5 {
		return errors.New("验证文件的MD5值不一致")
	}
	return nil
}

// 写入附件记录
func write(ctx context.Context, obj *FileObject) (models *entity.BlFile, err error) {
	uid := ctx.Value(consts.CtxUId).(uint64)
	models = &entity.BlFile{
		UserId:      uid,
		FileStorage: config.Drive,
		FileName:    obj.Meta.Filename,
		FilePath:    obj.Key,
		FileSize:    uint64(obj.Meta.Size),
		FileFormat:  obj.Meta.Kind,
		FileType:    obj.Meta.MimeType,
		FileHash:    obj.Hash,
		FileMd5:     obj.Meta.Md5,
	}
	err = dao.BlFile.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		var id int64
		if id, err = dao.BlFile.Ctx(ctx).Data(models).OmitEmptyData().InsertAndGetId(); err != nil {
			return err
		}
		models.Id = uint64(id)
		return nil
	})
	return
}

// 检查附件是否存在
func hasFile(ctx context.Context, key string) (res *entity.BlFile, err error) {
	var (
		cls = dao.BlFile.Columns()
		uid = ctx.Value(consts.CtxUId).(uint64)
	)
	err = dao.BlFile.Ctx(ctx).
		Where(cls.FilePath, key).
		Where(cls.FileStorage, config.Drive).
		Where(cls.UserId, uid).
		Scan(&res)
	if err != nil {
		return nil, errors.New("查询文件记录出现错误")
	}
	return
}
